﻿// -----------------------------------------------------------------------
// <copyright file="Serializer{TParameters}.cs" company="none">
// Copyright (c) 2012 M. Alberti, xanatos(at)live.it
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// </copyright>
// -----------------------------------------------------------------------

namespace FluentSerializer
{
    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Linq.Expressions;
    using System.Reflection;
    using CollectionsHelpers;
    using Utilities;

    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="TParameters"></typeparam>
    public class Serializer<TParameters> : Serializer
    {
        /// <summary>
        /// 
        /// </summary>
        public readonly ConcurrentDictionary<Type, Descriptor> LocalSerializers = new ConcurrentDictionary<Type, Descriptor>();

        /// <summary>
        /// 
        /// </summary>
        public readonly Dictionary<Type, Generator> LocalGenerators = new Dictionary<Type, Generator>();

        /// <summary>
        /// 
        /// </summary>
        public readonly Type Helper;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="helper"></param>
        public Serializer(Type helper = null)
        {
            this.Helper = helper ?? typeof(VariableLengthInt32<>);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        public new void PreCompile(Type type)
        {
            this.GetSerializer(type, type, this.Helper);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="stream"></param>
        /// <param name="obj"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public T Deserialize<T>(BinaryReaderStream stream, T obj, TParameters parameters)
        {
            Descriptor descriptor = this.GetSerializer(typeof(T), typeof(T), this.Helper);

            if (descriptor.IsUniversal)
            {
                var deserialize = (Func<BinaryReaderStream, T, T>)descriptor.Deserialize;
                return deserialize(stream, obj);
            }

            var deserialize2 = (Func<BinaryReaderStream, T, TParameters, T>)descriptor.Deserialize;
            return deserialize2(stream, obj, parameters);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="stream"></param>
        /// <param name="obj"></param>
        /// <param name="parameters"></param>
        public void Serialize<T>(BinaryWriterStream stream, T obj, TParameters parameters)
        {
            Descriptor descriptor = this.GetSerializer(typeof(T), typeof(T), this.Helper);

            if (descriptor.IsUniversal)
            {
                var serialize = (Action<BinaryWriterStream, T>)descriptor.Serialize;
                serialize(stream, obj);
                return;
            }

            var serialize2 = (Action<BinaryWriterStream, T, TParameters>)descriptor.Serialize;
            serialize2(stream, obj, parameters);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public int Size<T>(T obj, TParameters parameters)
        {
            Descriptor descriptor = this.GetSerializer(typeof(T), typeof(T), this.Helper);

            if (descriptor.IsUniversal)
            {
                var size = (Func<T, int>)descriptor.Size;
                return size(obj);
            }

            var size2 = (Func<T, TParameters, int>)descriptor.Size;
            return size2(obj, parameters);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <param name="requestedType"></param>
        /// <param name="helper"></param>
        /// <returns></returns>
        public override Descriptor GetSerializer(Type type, Type requestedType, Type helper)
        {
            Descriptor descriptor;

            if (typeof(IModifier).IsAssignableFrom(requestedType))
            {
                if (requestedType.IsGenericType)
                {
                    if (requestedType.IsGenericTypeDefinition)
                    {
                        if (type.IsValueType && type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
                        {
                            requestedType = requestedType.MakeGenericType(type.GetGenericArguments()[0]);
                        }
                        else
                        {
                            requestedType = requestedType.MakeGenericType(type);
                        }
                    }
                    else
                    {
                        var arg = requestedType.GetGenericArguments()[0];

                        if (arg.IsGenericType && arg.GetGenericTypeDefinition() == typeof(Nullable<>))
                        {
                            requestedType = typeof(Optional<>).MakeGenericType(arg.GetGenericArguments()[0]);
                        }
                    }
                }
            }
            else if (helper != null && requestedType.IsAssignableFrom(typeof(ICollection<>)))
            {
                requestedType = helper.MakeGenericType(requestedType);
            }
            else if (requestedType.IsValueType)
            {
                if (requestedType.IsGenericType)
                {
                    if (requestedType.GetGenericTypeDefinition() == typeof(Nullable<>))
                    {
                        requestedType = typeof(Optional<>).MakeGenericType(requestedType.GetGenericArguments()[0]);
                    }
                }
            }

            if (this.LocalSerializers.TryGetValue(requestedType, out descriptor))
            {
                return descriptor;
            }

            if (Serializer.UniversalSerializers.TryGetValue(requestedType, out descriptor))
            {
                return descriptor;
            }

            object[] attr = requestedType.GetCustomAttributes(typeof(FluentSerializableAttribute), false);

            if (attr.Length != 0)
            {
                var attr2 = (FluentSerializableAttribute)attr[0];
                Type type2 = attr2.Serializer;
                Type helper2 = attr2.CollectionHelper ?? helper;

                Type @interface = typeof(ISerializer<,>).MakeGenericType(requestedType, typeof(TParameters));
                int ix = Array.IndexOf(type2.GetInterfaces(), @interface);

                if (ix == -1)
                {
                    throw new NotSupportedException();
                }

                ConstructorInfo constructor = type2.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);

                if (constructor == null)
                {
                    throw new NotSupportedException();
                }

                object serializer2 = constructor.Invoke(null);

                MethodInfo getSerializer = type2.GetInterfaceMap(@interface).TargetMethods[0];

                Expression expression = (Expression)getSerializer.Invoke(serializer2, null);

                var visitorRead = new Visitor(this, Operation.Deserialize, helper2, false);
                var deserializeExpression = (LambdaExpression)visitorRead.StartVisit(expression, requestedType);

                var visitorWrite = new Visitor(this, Operation.Serialize, helper2, false);
                var serializeExpression = (LambdaExpression)visitorWrite.StartVisit(expression, typeof(void));

                var visitorSize = new Visitor(this, Operation.Size, helper2, false);
                var sizeExpression = (LambdaExpression)visitorSize.StartVisit(expression, typeof(int));

                descriptor = new Descriptor(requestedType, false, deserializeExpression, requestedType.IsValueType ? DeserializationHandling.RequireAssignment : DeserializationHandling.None, serializeExpression, sizeExpression);
                this.LocalSerializers[descriptor.Type] = descriptor;

                return descriptor;
            }

            Type realType;

            if (requestedType.IsGenericType)
            {
                if (typeof(ICollectionHelper).IsAssignableFrom(requestedType))
                {
                    realType = typeof(ICollectionHelper);
                }
                else
                {
                    realType = requestedType.GetGenericTypeDefinition();
                }
            }
            else
            {
                if (requestedType.IsEnum)
                {
                    realType = typeof(Enum);
                }
                else if (requestedType.IsArray)
                {
                    realType = typeof(Array);
                }
                else
                {
                    realType = requestedType;
                }
            }

            Generator generator;

            if (this.LocalGenerators.TryGetValue(realType, out generator))
            {
                descriptor = generator(this, typeof(TParameters), requestedType, helper);
                this.LocalSerializers[descriptor.Type] = descriptor;
                return descriptor;
            }

            if (Serializer.UniversalGenerators.TryGetValue(realType, out generator))
            {
                descriptor = generator(this, typeof(TParameters), requestedType, helper);

                if (descriptor.IsUniversal)
                {
                    Serializer.UniversalSerializers[descriptor.Type] = descriptor;
                }
                else
                {
                    this.LocalSerializers[descriptor.Type] = descriptor;
                }

                return descriptor;
            }

            throw new NotSupportedException();
        }

        /// <summary>
        /// 
        /// </summary>
        public new void Dump()
        {
            Serializer.Dump();

            foreach (var descriptor in this.LocalSerializers)
            {
                Serializer.Dump(descriptor, "L");
            }
        }
    }
}